/*
 Copyright (c) 2013 MeherTJ G. All rights reserved.
 License: LGPL for personnal study or free software.
 */

#pragma once

#include "zen_gles2_shader.h"
#include "zen_gles2_buffer.h"
#include "zen_gles2_render.h"
#include "zen_color.h"

namespace Zen { namespace GL {
	
#define SHADER_BIND_ATT(name, setName) \
protected: GLint name = -1; \
public: void setName(size_t each_count, eType type, bool norm, size_t stride, size_t off) { \
if(name >= 0) \
Render::Get()->setVertexBuffer(name, each_count, type, norm, stride, off); } \
void setName##WithData(size_t each_count, eType type, bool norm, size_t stride, void const * data) { \
if(name >= 0) \
Render::Get()->setVertexData(name, each_count, type, norm, stride, data); \
}
	
#define SHADER_BIND_UNI(name, setName, VertexData) \
protected: GLint name = -1; \
public: void setName(VertexData const & v) { \
if(name >= 0) \
Render::Get()->setUniform(name, v); \
}
	
#define SHADER_BIND_TEX(name, setName) \
protected: GLint name = -1; \
public: void setName(int sampler, GLuint texture_id) { \
if(name >= 0) \
Render::Get()->setSampler(name, sampler, texture_id); \
}
	
#define SHADER_PREPARE() \
virtual prepare() \

	class ShaderKit {
	public:
		ShaderProgram & program() {
			return m_program;
		}
		void bindProgram(VShader const & vshader, FShader const & fshader) {
			m_program.makeAttachAndLink(vshader, fshader);
		}
		void bindProgram(std::string const & vshader, std::string const & fshader) {
			m_program.makeAttachAndLink(vshader.c_str(), fshader.c_str());
		}
		
		void active() {
			Render::Get()->activeProgram(m_program.getID());
		}
		void render(eVMode mode, size_t index, size_t count) {
			Render::Get()->drawArrays(mode, 0, count);
		}
		// sub class could override and do something like binding extra args.
		virtual void init() {
		}
		
		virtual void prepare() {
		}
	protected:
		ShaderProgram m_program;
	};
	/**
	 @arg
	 attributes:
	 - a_coord: vertex coords
	 - a_color: vertex colors
	 uniforms:
	 - u_transfrom: matrix transform
	 - u_color: global color
	 */
	class ShaderKitColors : public ShaderKit
	{
	public:
		SHADER_BIND_ATT(a_coord, bindAttCoord);
		SHADER_BIND_ATT(a_color, bindAttColor);
		
		SHADER_BIND_UNI(u_transform, setUniMat4, Mat4);
		SHADER_BIND_UNI(u_color, setUniColor, Vec4);
		
	public:
		typedef std::shared_ptr<ShaderKitColors> Shared;
		
		static Shared GetNormal();

		static Shared GetToGrey();

		static Shared Create(std::string const & v, std::string const & f);

	public:
		struct Selector {
			virtual Shared operator()(ePixel) = 0;
		};

		typedef std::function<Shared (ePixel)> SelectFunction;

		typedef std::shared_ptr<Selector> SharedSelector;
		
		struct SelectorNormal : public Selector {
		public:

			virtual Shared operator()(ePixel format) {
				return GetNormal();
			}
		};
		struct SelectorToGrey : public Selector {
		public:

			virtual Shared operator()(ePixel format) {
				return GetToGrey();
			}
		};

	protected:
		ShaderKitColors() = default;
		ShaderKitColors(ShaderKitColors &) = delete;
		void operator = (int) = delete;
	};
	/**
	 @arg
	 attributes:
	 - a_coord: vertex coord
	 - a_color: vertex color.
	 - a_point_size: vertex point size.
	 uniforms:
	 - u_transform: transfrom matrix.
	 - u_point_size_ratio: scaling for size.
	 - u_color: global color.
	 - u_sampler: texture for point.
	 */
	class ShaderKitPoints : public ShaderKit
	{
	public:
		SHADER_BIND_ATT(a_coord, bindAttCoord);
		SHADER_BIND_ATT(a_color, bindAttColor);
		SHADER_BIND_ATT(a_point_size, bindAttPointSize);
		
		SHADER_BIND_UNI(u_transform, setUniMat4, Mat4);
		SHADER_BIND_UNI(u_color, setUniColor, Vec4);
		SHADER_BIND_UNI(u_point_size_ratio, setUniPointSizeRatio, Vec2);
		SHADER_BIND_TEX(u_sampler, setUniSampler);

	public:
		typedef std::shared_ptr<ShaderKitPoints> Shared;
		
		static Shared GetNormal();

		static Shared GetToGrey();

		static Shared GetWithoutTexture();

		static Shared GetWithAlphaTexture();

		static Shared GetWithGreyTexture();

		static Shared Create(std::string const & v, std::string const & f);

	public:
		struct Selector {
			virtual Shared operator()(ePixel) = 0;
		};
		
		typedef std::function<Shared (ePixel)> SelectFunction;

		typedef std::shared_ptr<Selector> SharedSelector;

		struct SelectorNormal : public Selector {
		public:

			virtual Shared operator()(ePixel format) {
				if(format == ePixel::None) return GetWithoutTexture();
				else if(format == ePixel::Grey) return GetWithAlphaTexture();
				return GetNormal();
			}
		};
		struct SelectorToGrey : public Selector {
		public:

			virtual Shared operator()(ePixel format) {
				if(format == ePixel::None) return GetWithoutTexture();
				else if(format == ePixel::Grey) return GetWithGreyTexture();
				return GetToGrey();
			}
		};

	protected:
		ShaderKitPoints() = default;
		ShaderKitPoints(ShaderKitPoints&) = delete;
		void operator = (int) = delete;
	};
	
	/**
	 @arg
	 attributes:
	 - a_coord: vertex coords
	 - a_tex_coord: coord in texture.
	 uniforms:
	 - u_transfrom: matrix transform
	 - u_color: global color
	 - u_sampler: texture
	 */
	class ShaderKitTexture : public ShaderKit
	{
	public:
		SHADER_BIND_ATT(a_coord, bindAttCoord);
		SHADER_BIND_ATT(a_tex_coord, bindAttTexCoord);
		
		SHADER_BIND_UNI(u_transform, setUniMat4, Mat4);
		SHADER_BIND_UNI(u_color, setUniColor, Vec4);
		SHADER_BIND_TEX(u_sampler, setUniSampler);
		SHADER_BIND_UNI(u_tex_rect, setUniTexRect, Vec4);
		
	public:
		typedef std::shared_ptr<ShaderKitTexture> Shared;

		static Shared GetNormal();

		static Shared GetToGrey();

		static Shared GetWithoutTexture();

		static Shared GetWithAlphaTexture();

		static Shared GetWithGreyTexture();

		static Shared Create(std::string const & v, std::string const & f);

	public:
		struct Selector {
			virtual Shared operator()(ePixel format) = 0;
		};

		typedef std::function<Shared (ePixel)> SelectFunction;

		typedef std::shared_ptr<Selector> SharedSelector;

		struct SelectorNormal : public Selector {
		public:

			virtual Shared operator()(ePixel format) {
				if(format == ePixel::None) return GetWithoutTexture();
				else if(format == ePixel::Grey) return GetWithAlphaTexture();
				return GetNormal();
			}
		};

		struct SelectorToGrey : public Selector {
		public:
			virtual Shared operator()(ePixel format) {
				if(format == ePixel::None) return GetWithoutTexture();
				else if(format == ePixel::Grey) return GetWithGreyTexture();
				return GetToGrey();
			}
		};

	protected:
		ShaderKitTexture() = default;
		ShaderKitTexture(ShaderKitTexture&) = delete;
		void operator = (int) = delete;
	};

#undef SHADER_BIND_TEX
#undef SHADER_BIND_UNI
#undef SHADER_BIND_ATT
}}

namespace Zen { namespace GL {

	template<typename _VertexData>
	struct BufferShell
	{
	protected:
		ArrayBuffer m_buffer;
		size_t m_count = 0;
	public:
		typedef _VertexData VertexData;
		
		enum { step = sizeof(VertexData) };
		
		ArrayBuffer & buffer() { return m_buffer; }
		
		size_t count() { return m_count; }
		
		void updateBuffer(VertexData const * data, size_t count)
		{
			if(m_count == count)
			{
				m_buffer.update(count * step, data);
			}
			else {
				m_buffer.create(count * step, data);
				m_count = count;
			}
		}
		void updateBuffer(VertexData const * data, size_t count, size_t index)
		{
			must(m_count >= count + index);
			if(m_count == count)
			{
				m_buffer.update(count*step, data, index*step);
			}
		}
		void bind()
		{
			Render::Get()->bindBuffer(m_buffer.getID());
		}
	};

}}
